#include <iostream>
#include <string>
#include <assert.h>
#include <SDL/SDL.h>
#include <SDL/SDL_ttf.h>
#include <map>
#include <vector>
#include "ui.h"
#include "gfx.h"

using namespace std;

//static void sanitiseString(string& str);
static UI::Fonts fonts;
static const int IN_MASK = 0xFF80, UNICODE_MASK = 0x7F;
static const unsigned REPEAT_DELAY 	= 200;	// delay before key repeats
static const unsigned REPEAT_TIME 	= 50;	// keyboard repeat time in ms

// default window colours
static const SDL_Color windowActiveTop 			= {127, 31, 31, 0};
static const SDL_Color windowActiveBottom 		= {0, 	0, 0, 0};
static const SDL_Color windowInactiveTop 		= {31, 	31, 31, 0};
static const SDL_Color windowInactiveBottom 	= {0, 	0, 	0, 	0};
static const SDL_Color windowActiveTitleBar		= {127, 0, 	0, 	0};
static const SDL_Color windowInactiveTitleBar	= {0, 	0, 	0, 	0};
static const SDL_Color windowBorder				= {0, 	0, 	0, 	0};

// default button colours
static const SDL_Color buttonActive				= {127,	31,	31,	0};
static const SDL_Color buttonInactive			= {31,	31,	31,	0};

// default inputbox colours
static const SDL_Colour inputBoxActive			= {63,	15,	15,	0};
static const SDL_Colour inputBoxInactive		= {15,	15,	15,	0};

extern unsigned int bpp;

void initUI() {
	if (TTF_Init() < 0) {
		cout << "SDL_ttf initialisation failed: [" << TTF_GetError()
			 << "] aborting.\n";
		exit(1);
	}
	atexit(TTF_Quit);
}

/*
// sanitise user inputted strings.
static void sanitiseString(string& str) {
	size_t pos;
	if (str.empty())
		return;
	// remove any stupid characters
	for (pos = 0; pos < str.length(); pos++) {
		if (str[pos] < 32 || str[pos] > 126)
			str.erase(pos);
	}
}
*/

namespace UI {
	// Fonts ===================================================================
	Fonts::Fonts() { }
	Fonts::~Fonts() { 


	}

	TTF_Font* Fonts::getFont(const unsigned size) {
		assert(fontDataUsers_[size] > 0);
		return fontData_[size];
	}

	// If something wants to use a font, it must request it first.
	void Fonts::requestFont(const unsigned size) {
		TTF_Font* font;
		// if the font is already in use, count this user and return
		if (fontDataUsers_[size] > 0) {
			++fontDataUsers_[size];
			return;
		}
		// as nothing else is using this font, it must be loaded
		font = TTF_OpenFont("fonts/LiberationSans-Regular.ttf", size);
		if(font == NULL) {
			cout << "Could not load font: [" << TTF_GetError() 
				<< "] aborting.\n";
			exit(1);
		}
		fontData_[size] = font;
		fontDataUsers_[size] = 1;
	}

	void Fonts::returnFont(unsigned size) {
		assert(fontDataUsers_[size] != 0);
		--fontDataUsers_[size];
		if (fontDataUsers_[size] == 0) {
			TTF_CloseFont(fontData_[size]);
			fontData_[size] = NULL;
		}
	}

	// TextBox =================================================================
	TextBox::TextBox(const char* text, int x, int y, int w, int h, 
			unsigned size) {
		x_ = x; y_ = y;
		w_ = w; h_ = h;
		view_ = 0;
		maxLines_ = 50;
		isEnabled_ = true;
		style_ = TTF_STYLE_NORMAL;
		isInvalidated_ = true;
		colour_.r = 0xff;
		colour_.g = 0xff;
		colour_.b = 0xff;
		borderColour_.r = 0;
		borderColour_.g = 0;
		borderColour_.b = 0;
		fillColour_.r = 15;
		fillColour_.g = 15;
		fillColour_.b = 15;
		isBorder_ = false;
		isCentred_ = false;
		isTextAdded_ = false;
		size_ = size;
		if (text != NULL) {
			text_ = new char[strlen(text) + 1];
			strcpy(text_, text);
		} else {
			text_ = NULL;
		}
		fonts.requestFont(size_);
	}
    
	TextBox::~TextBox() {
		fonts.returnFont(size_);
		delete[] text_;
		while(lines_.size() > 0) {
			SDL_FreeSurface(lines_.front());
			lines_.erase(lines_.begin());
		}
	}

	// This function will blit the textbox's text onto the passed surface
	void TextBox::blit(SDL_Surface* surface) {
		int maxLines;
		if (isBorder_ == true) {
			fillRect(surface, x_, y_, w_, h_, SDL_MapRGB(surface->format, 
					fillColour_.r, fillColour_.g, fillColour_.b));
			fillRect(surface, x_, y_, w_, 1, SDL_MapRGB(surface->format, 
					borderColour_.r, borderColour_.g, borderColour_.b));
			fillRect(surface, x_, y_ + h_ - 1, w_, 1, SDL_MapRGB(surface->format
					,borderColour_.r, borderColour_.g, borderColour_.b));
			fillRect(surface, x_, y_, 1, h_, SDL_MapRGB(surface->format, 
					borderColour_.r, borderColour_.g, borderColour_.b));
			fillRect(surface, x_ + w_ - 1, y_, 1, h_, SDL_MapRGB(surface->format
					,borderColour_.r, borderColour_.g, borderColour_.b));
		}
		if ((isEnabled_ == false) || (text_ == NULL) || (strlen(text_) == 0))
			return;
		// if something has changed, like the text, our surfaces are invalidated
		// as a result, we must rerender it.
		if (isInvalidated_ == true) {
			render();
			isInvalidated_ = false;
		}
		SDL_Rect dest;
		// now we blit all the lines that fit into the textbox
		maxLines = (h_ / TTF_FontLineSkip(fonts.getFont(size_)));

		if (isTextAdded_ == true) {
			if (maxLines <= (int)lines_.size())
				view_ = lines_.size() - maxLines;
			else
				view_ = 0;
			isTextAdded_ = false;
		}

		for (int i = view_; i < (int)lines_.size(); i++) {
			if (i == (maxLines + view_))
				break;
			if (isCentred_ == true)
				dest.x = x_ + ((w_ - lines_.at(i)->w) / 2);
			else
				dest.x = x_;
			dest.y = y_ + ((i - view_) 
					* TTF_FontLineSkip(fonts.getFont(size_)));
			if (SDL_BlitSurface(lines_.at(i), NULL, surface, &dest) < 0) {
				std::cout << "surface blit failed:" << SDL_GetError() << "\n";
				exit(1);
			}
		}
		
	}

	void TextBox::render() {
		int newLine = 0;    // index to beginning of the new line
		int w;              // temporarily stores the width of a character
		int currentWidth;   // the width of the current line
		char character[2];
		char* line;
		SDL_Surface* tempLine;
		// first clear the old lines, and free their surfaces
		while(lines_.size() > 0) {
			SDL_FreeSurface(lines_.front());
			lines_.erase(lines_.begin());
		}
		// if necessary, set the style
		if (TTF_GetFontStyle(fonts.getFont(size_)) != style_) {
			TTF_SetFontStyle(fonts.getFont(size_), style_);
		}
        // divide the text up into lines.
		currentWidth = 0;
		for (unsigned i = 0; i <= strlen(text_); i++) {
			// find the width of each character
			character[0] = text_[i];
			character[1] = '\0';
			if(TTF_SizeUTF8(fonts.getFont(size_), character, &w, NULL) 
					== -1) {
				std::cout << "TTF_SizeUTF8() failed\n";
				exit(1);
			}
			// is the current line just about to overflow the width?
			if ((((unsigned)currentWidth + w) > w_)
					|| (i == strlen(text_)) || (text_[i] == '\n')) {
				// if so, render it to a surface and store it.
				line = new char[(i - newLine) + 1];
				// strip newline characters
				if (text_[newLine] == '\n') {
					memcpy(line, text_ + newLine + 1, i - newLine - 1);
					line[i - newLine - 1] = '\0';
				} else {
					memcpy(line, text_ + newLine, i - newLine);
					line[i - newLine] = '\0';
				}
				// dont render empty lines (this actually fails)
				if (strlen(line) == 0)
					break;
				currentWidth = 0;
				tempLine = TTF_RenderUTF8_Blended(
						fonts.getFont(size_), line, colour_);
				if (tempLine == NULL) {
					std::cout << "TTF_RenderUTF8_Blended() failed " 
							<< TTF_GetError() << "\n";
					exit(1);
				}
				lines_.push_back(tempLine);
				newLine = i;
				delete[] line;
			}
			currentWidth += w;
		}
	}

	void TextBox::tick() {

	}

	// returns the minimum height that should be allowed for the text
	unsigned TextBox::getMinimumHeight() {
		if (isInvalidated_ == true) {
			render();
			isInvalidated_ = false;
		}
		return (lines_.size() * TTF_FontLineSkip(fonts.getFont(size_)));
	}

	void TextBox::setText(const char* text) {
		if (text_ != NULL)
			delete[] text_;
		text_ = new char[strlen(text) + 1];
		strcpy(text_, text);
		isInvalidated_ = true;
    }

	const char* TextBox::getText() const {
		return text_;
	}

	// this function was surprisingly annoying to write.
	// its also slow and not very nice
	// I can see why people keep telling me to use std::string now.
	void TextBox::addText(const char* text) {
		unsigned lines = 1, truncationLines = 0;
		char* temp;
		temp = new char[strlen(text_) + strlen(text) + 1];
		strcpy(temp, text_);
		strcpy(temp + strlen(text_), text);
		isInvalidated_ = true;
		delete[] text_;
		text_ = temp;
		// count the number of lines we've got.
		for (unsigned i = 0; i < strlen(text_); i++) {
			if (text_[i] == '\n')
				++lines;
		}
		// if we've got too many lines...
		if (lines > maxLines_) {
			// count through the lines we need to truncate
			for (unsigned i = 0; i < strlen(text_); i++) {
				if (text_[i] == '\n') {
					++truncationLines;
					// if we have counted enough lines...
					if (truncationLines == (lines - maxLines_)) {
						// cut them all out
						memmove(text_, text_ + i + 1, strlen(text_) - i);
						break;
					}
				}
			}
		}
		isTextAdded_ = true;
	}

	void TextBox::setX(const int x) {
		x_ = x;
	}

	void TextBox::setY(const int y) {
		y_ = y;
	}

	void TextBox::setWidth(const unsigned int w) {
		w_ = w;
	}

	int TextBox::getWidth() const {
		return w_;
	}

	void TextBox::setHeight(const unsigned int h) {
		h_ = h;
	}

	int TextBox::getHeight() const {
		return h_;
	}

	int TextBox::getX() const {
		return x_;
	}

	int TextBox::getY() const {
		return y_;
	}

	void TextBox::setSize(const int size) {
		fonts.returnFont(size_);
		size_ = size;
		fonts.requestFont(size_);
		isInvalidated_ = true;
	}

	int TextBox::getSize() const {
		return size_;
	}

	void TextBox::setColour(const Uint8 r, const Uint8 g, const Uint8 b) {
		colour_.r = r;
		colour_.g = g;
		colour_.b = b;
		isInvalidated_ = true;
	}

	void TextBox::setBold() {
		style_ |= TTF_STYLE_BOLD;
		isInvalidated_ = true;
	}

	void TextBox::setCentred() {
		isCentred_ = true;
	}

	void TextBox::unsetCentred() {
		isCentred_ = false;
	}

	void TextBox::unsetBold() {
		style_ &= ~(0 | TTF_STYLE_BOLD);
		isInvalidated_ = true;
	}

	void TextBox::setItalic() {
		style_ |= TTF_STYLE_ITALIC;
		isInvalidated_ = true;
	}

	void TextBox::unsetItalic() {
		style_ &= ~(0 | TTF_STYLE_ITALIC);
		isInvalidated_ = true;
	}

	void TextBox::setUnderline() {
		style_ |= TTF_STYLE_UNDERLINE;
		isInvalidated_ = true;
	}

	void TextBox::unsetUnderline() {
		style_ &= ~(0 | TTF_STYLE_UNDERLINE);
		isInvalidated_ = true;
	}

	void TextBox::enable() {
		isEnabled_ = true;
	}

	void TextBox::disable() {
		isEnabled_ = false;
	}

	bool TextBox::getIsInvalidated() {
		return isInvalidated_;
	}

	void TextBox::setBorderColour(const SDL_Color* borderColour) {
		borderColour_.r = borderColour->r;
		borderColour_.g = borderColour->g;
		borderColour_.b = borderColour->b;
	}

	void TextBox::setFillColour(const SDL_Color* fillColour) {
		fillColour_.r = fillColour->r;
		fillColour_.g = fillColour->g;
		fillColour_.b = fillColour->b;
	}

	void TextBox::setIsBorder(const bool isBorder) {
		isBorder_ = isBorder;
	}

	int TextBox::getView() {
		return view_;
	}

	void TextBox::setView(const int view) {
		view_ = view;
		if (view_ < 0)
			view_ = 0;
		else if ((unsigned)view_ > lines_.size())
			view_ = lines_.size();
	}

	// InputBox ================================================================
	InputBox::InputBox(int x, int y, int w, int h, unsigned size, 
			unsigned maxLength, InputBoxEvent* returnEvent) 
			: TextBox(NULL, x, y, w, h, size) {
		maxLength_ = maxLength;
		text_ = new char[maxLength_ + 1];
		text_[0] = '\0';
		returnEvent_ = returnEvent;
		keyDownTime_ = 0;
		repeatTime_ = 0;
		//lastKey_ = SDLK_0;	// arbitrary default
		isBorder_ = true;
		isKeyRepeating_ = false;
		isKeyDown_ = false;
	}

	InputBox::~InputBox() {
		delete[] text_;
	}

	void InputBox::keyEvent(const SDL_KeyboardEvent &event) {
		// set a keyboard repeat time
		if (event.type == SDL_KEYUP) {
			isKeyRepeating_ = false;
			isKeyDown_ = false;
			return;
		}
		if (isEnabled_ == false)
			return;
		isKeyDown_ = true;
		handleKey(event.keysym);
		keyDownTime_ = SDL_GetTicks();
		repeatTime_ = SDL_GetTicks();
		// this is actually not very nice
		memcpy(&lastKeySym_, &event.keysym, sizeof(SDL_keysym));
	}

	void InputBox::handleKey(const SDL_keysym &keysym) {
		switch(keysym.sym) {
			case SDLK_BACKSPACE:
			case SDLK_DELETE:
				// remove last character
				if (strlen(text_) != 0) {
					text_[strlen(text_) - 1] = '\0';
					isInvalidated_ = true;
				}
				break;
			case SDLK_RETURN:
				returnEvent_->handle(text_, this);
				break;
			default:
				if (strlen(text_) == maxLength_ + 1)
					break;
				// non unicode translatable key. ignore
				if (keysym.unicode == 0)
					break;
				// international character. ignore
				if ((keysym.unicode & IN_MASK) == IN_MASK)
					break;
				int length = strlen(text_);
				text_[length] = (char) (keysym.unicode 
						& UNICODE_MASK);
				text_[length + 1] = '\0';
				isInvalidated_ = true;
				break;
		}
	}

	void InputBox::tick() {
		if (isKeyDown_ == true) {
			if (isKeyRepeating_ == false) {
				if ((SDL_GetTicks() - keyDownTime_) < REPEAT_DELAY)
					return;
				else
					isKeyRepeating_ = true;
			} 
			if ((SDL_GetTicks() - repeatTime_) < REPEAT_TIME)
				return;
			handleKey(lastKeySym_);
			repeatTime_ = SDL_GetTicks();
		}
	}

	bool InputBox::getIsActive() {
		return isActive_;
	}

	void InputBox::setIsActive(const bool isActive) {
		isActive_ = isActive;
	}

	void InputBox::setText(const char* text) {
		strcpy(text_, text);
		isInvalidated_ = true;
    }

	// Button ==================================================================
	Button::Button(const int x, const int y, const int w, const int h, 
			const char* text, const int textSize, Event* clickEvent) {
		x_ = x; y_ = y;
		w_ = w; h_ = h;
		textBox_ = new TextBox(text, x + 2, y + 2, w - 2, h - 2, textSize);
		clickEvent_ = clickEvent;
		isEnabled_ = true;
		textColour_.r = 255; textColour_.g = 255; textColour_.b = 255;
		textBox_->setColour(textColour_.r, textColour_.g, textColour_.b);
		textBox_->setCentred();
		colour_.r = buttonActive.r; 
		colour_.g = buttonActive.g; 
		colour_.b = buttonActive.b;
		isClicked_ = false;
		surface_ = NULL;
		surfaceClicked_ = NULL;
		isInvalidated_ = true;
}

	Button::~Button() {
		delete textBox_;
		if (surface_ != NULL)
			SDL_FreeSurface(surface_);
	}

	void Button::blit(SDL_Surface* surface) {
		// if the button hasnt been already, render it
		if (surface_ == NULL) {
			SDL_Color bright;
			SDL_Color dark;
			surface_ = SDL_CreateRGBSurface(SDL_SWSURFACE, w_, h_, bpp, 0, 0, 0,
					0);
			// fill button
			fillRect(surface_, 0, 0, w_, h_, SDL_MapRGB(surface_->format, 
					colour_.r, colour_.g, colour_.b));
			// draw button "border"
			(colour_.r * 2) > 255 ? bright.r = 255 : bright.r = colour_.r * 2;
			(colour_.g * 2) > 255 ? bright.g = 255 : bright.g = colour_.g * 2;
			(colour_.b * 2) > 255 ? bright.b = 255 : bright.b = colour_.b * 2;
			dark.r = colour_.r / 2;
			dark.g = colour_.g / 2;
			dark.b = colour_.b / 2;

			fillRect(surface_, 0, 0, w_, 1, SDL_MapRGB(surface_->format, 
					bright.r, bright.g, bright.b));
			fillRect(surface_, 0, h_ - 1, w_, h_, SDL_MapRGB(surface_->format, 
					dark.r, dark.g, dark.b));

			fillRect(surface_, 0, 0, 1, h_, SDL_MapRGB(surface_->format, 
					bright.r, bright.g, bright.b));
			fillRect(surface_, w_ - 1, 0, w_, h_, SDL_MapRGB(surface_->format, 
					dark.r, dark.g, dark.b));
		}

		if (surfaceClicked_ == NULL) {
			SDL_Colour clicked;
			clicked.r = colour_.r / 2;
			clicked.g = colour_.g / 2;
			clicked.b = colour_.b / 2;
			SDL_Color bright;
			SDL_Color dark;
			surfaceClicked_ = SDL_CreateRGBSurface(SDL_SWSURFACE, w_, h_, bpp, 
					0, 0, 0, 0);
			// fill button
			fillRect(surfaceClicked_, 0, 0, w_, h_, SDL_MapRGB(surface_->format, 
					clicked.r, clicked.g, clicked.b));
			// draw button "border"
			(clicked.r * 2) > 255 ? bright.r = 255 : bright.r = clicked.r * 2;
			(clicked.g * 2) > 255 ? bright.g = 255 : bright.g = clicked.g * 2;
			(clicked.b * 2) > 255 ? bright.b = 255 : bright.b = clicked.b * 2;
			dark.r = clicked.r / 2;
			dark.g = clicked.g / 2;
			dark.b = clicked.b / 2;

			fillRect(surfaceClicked_, 0, 0, w_, 1, SDL_MapRGB(surface_->format, 
					dark.r, dark.g, dark.b));
			fillRect(surfaceClicked_, 0, h_ - 1, w_, h_, SDL_MapRGB(
					surface_->format, bright.r, bright.g, bright.b));

			fillRect(surfaceClicked_, 0, 0, 1, h_, SDL_MapRGB(surface_->format, 
					dark.r, dark.g, dark.b));
			fillRect(surfaceClicked_, w_ - 1, 0, w_, h_, SDL_MapRGB(
					surface_->format, bright.r, bright.g, bright.b));
		}

		SDL_Rect dest;
		dest.x = x_;
		dest.y = y_;
		if (isClicked_ == false) {
			if (SDL_BlitSurface(surface_, NULL, surface, &dest) < 0) {
				std::cout << "surface blit failed:" << SDL_GetError() << "\n";
				exit(1);
			}
		} else {
			if (SDL_BlitSurface(surfaceClicked_, NULL, surface, &dest) < 0) {
				std::cout << "surface blit failed:" << SDL_GetError() << "\n";
				exit(1);
			}
		}
		isInvalidated_ = false;
		textBox_->blit(surface);
	}

	void Button::click() {
		if ((isClicked_ == false) && (isEnabled_ == true)) {
			isInvalidated_ = true;
			isClicked_ = true;
		}
	}
	
	void Button::release() {
		if (isClicked_ == true) {
			clickEvent_->handle();
			isClicked_ = false;
			isInvalidated_ = true;
		}
	}

	void Button::tick() {

	}

	void Button::enable() {
		isEnabled_ = true;
	}

	void Button::disable() {
		isEnabled_ = false;
	}

	bool Button::getIsInvalidated() {
		return isInvalidated_;
	}

	void Button::setText(const char* text) {
		isInvalidated_ = true;
		textBox_->setText(text);
	}

	void Button::setX(const int x) {
		isInvalidated_ = true;
		x_ = x;
	}

	void Button::setY(const int y) {
		isInvalidated_ = true;
		y_ = y;
	}

	void Button::setWidth(const unsigned int w) {
		isInvalidated_ = true;
		w_ = w;
	}

	int Button::getWidth() const {
		return w_;
	}

	void Button::setHeight(const unsigned int h) {
		isInvalidated_ = true;
		h_ = h;
	}

	int Button::getHeight() const {
		return h_;
	}

	int Button::getX() const {
		return x_;
	}

	int Button::getY() const {
		return y_;
	}

	void Button::setColour(const SDL_Color* colour) {
		colour_.r = colour->r;
		colour_.g = colour->g;
		colour_.b = colour->b;

		SDL_FreeSurface(surface_);
		surface_ = NULL;
		SDL_FreeSurface(surfaceClicked_);
		surfaceClicked_ = NULL;
	}

	void Button::setTextColour(const SDL_Color* textColour) {
		textColour_.r = textColour->r;
		textColour_.g = textColour->g;
		textColour_.b = textColour->b;
		textBox_->setColour(textColour_.r, textColour_.g, textColour_.b);
	}

	// Image ===================================================================
	Image::Image(int x, int y, int w, int h, const char* fn) {
		x_ = x; y_ = y;
		w_ = w; h_ = h;
		if (fn != NULL) {
			surface_ = loadBitmap(fn);
			w_ = surface_->w;
			h_ = surface_->h;
		} else {
			surface_ = NULL;
		}
		isInvalidated_ = true;
	}

	Image::~Image() {
		if (surface_ != NULL)
			SDL_FreeSurface(surface_);
	}

	void Image::blit(SDL_Surface* surface) {
		if (surface_ != NULL) {
			SDL_Rect dest;
			dest.x = x_;
			dest.y = y_;
			if (SDL_BlitSurface(surface_, NULL, surface, &dest) < 0) {
				std::cout << "surface blit failed:" << SDL_GetError() << "\n";
				exit(1);
			}
		}
		isInvalidated_ = false;
	}

	void Image::tick() {

	}

	void Image::loadBMP(const char* fn) {
		if (surface_ != NULL)
			SDL_FreeSurface(surface_);
		if (fn != NULL) {
			surface_ = loadBitmap(fn);
			w_ = surface_->w;
			h_ = surface_->h;
		} else {
			surface_ = NULL;
		}
		isInvalidated_ = true;
	}

	bool Image::getIsInvalidated() {
		return isInvalidated_;
	}

	// Window ==================================================================
	Window::Window(int x, int y, int w, int h, const char* title) {
		x_ = x; y_ = y;
		w_ = w; h_ = h;
		if (title != NULL) {
			title_ = new TextBox(title, 1, 1, w - 1, h - 1, 12);
			title_->setCentred();
		} else {
			title_ = NULL;
		}
		isClosable_ = false;
		isMoveable_ = false;
		isResizable_ = false;
		isInvalidated_ = true;
		isActive_ = false;
		surfaceActive_ = NULL;
		surfaceInactive_ = NULL;
		surfaceAll_ = NULL;
		alpha_ = 195;
	}

	Window::~Window() {
		if (title_ != NULL)
			delete title_;
		if (surfaceActive_ != NULL)
			SDL_FreeSurface(surfaceActive_);
		if (surfaceInactive_ != NULL)
			SDL_FreeSurface(surfaceInactive_);
		if (surfaceAll_ != NULL)
			SDL_FreeSurface(surfaceAll_);
		while(textBoxes_.size() > 0) {
			delete textBoxes_.front();
			textBoxes_.erase(textBoxes_.begin());
		}
		while(inputBoxes_.size() > 0) {
			delete inputBoxes_.front();
			inputBoxes_.erase(inputBoxes_.begin());
		}
		while(buttons_.size() > 0) {
			delete buttons_.front();
			buttons_.erase(buttons_.begin());
		}
		while(images_.size() > 0) {
			delete images_.front();
			images_.erase(images_.begin());
		}
	}

	void Window::blit(SDL_Surface* surface) {
		bool objectsInvalidated = false;
		if (isInvalidated_ == true) {
			render();
			isInvalidated_ = false;
		}
		for (int i = 0; i < (int)textBoxes_.size(); i++) {
			if (textBoxes_.at(i)->getIsInvalidated() == true)
				objectsInvalidated = true;
		}
		for (int i = 0; i < (int)inputBoxes_.size(); i++) {
			if (inputBoxes_.at(i)->getIsInvalidated() == true)
				objectsInvalidated = true;
		}
		for (int i = 0; i < (int)buttons_.size(); i++) {
			if (buttons_.at(i)->getIsInvalidated() == true)
				objectsInvalidated = true;
		}
		for (int i = 0; i < (int)images_.size(); i++) {
			if (images_.at(i)->getIsInvalidated() == true)
				objectsInvalidated = true;
		}
		if ((objectsInvalidated == true) || (surfaceAll_ == NULL))
			renderAll();
		
		SDL_Rect dest;
		dest.x = x_;
		dest.y = y_;
		if (SDL_BlitSurface(surfaceAll_, NULL, surface, &dest) < 0) {
			std::cout << "surface blit failed:" << SDL_GetError() << "\n";
			exit(1);
		}
	}

	// this function renders surfaces for the background of the window
	// ie. the window itself. Two surfaces are created, one for the active
	// window, and one for the inactive one.
	void Window::render() {
		// if necessary clear the old surfaces first and then create new ones
		if (surfaceActive_ != NULL) {
			if ((surfaceActive_->w != (int)w_) 
					|| (surfaceActive_->h != (int)h_)) {
				SDL_FreeSurface(surfaceActive_);
				surfaceActive_ = SDL_CreateRGBSurface(SDL_SWSURFACE, w_, h_, 
						bpp, 0, 0, 0, 0);
			}
		} else {
			surfaceActive_ = SDL_CreateRGBSurface(SDL_SWSURFACE, w_, h_, 
					bpp, 0, 0, 0, 0);
		}

		if (surfaceInactive_ != NULL) {
			if ((surfaceInactive_->w != (int)w_) 
					|| (surfaceInactive_->h != (int)h_)) {
				SDL_FreeSurface(surfaceInactive_);
				surfaceInactive_ = SDL_CreateRGBSurface(SDL_SWSURFACE, w_, h_, 
						bpp, 0, 0, 0, 0);
			}
		} else {
			surfaceInactive_ = SDL_CreateRGBSurface(SDL_SWSURFACE, w_, h_, 
					bpp, 0, 0, 0, 0);
		}

		unsigned char r, g, b;
		unsigned x, y;
		unsigned pixel;
		// first the active window is rendered
		for (y = 0; y < h_; ++y) {
			for (x = 0; x < w_; ++x) {
				// draw a gradient for the window
				// rounding is causing this not to work as well as it should
				r = windowActiveTop.r - ((windowActiveTop.r -
						windowActiveBottom.r) * y / h_);
				g = windowActiveTop.g - ((windowActiveTop.g -
						windowActiveBottom.g) * y / h_);
				b = windowActiveTop.b - ((windowActiveTop.b -
						windowActiveBottom.b) * y / h_);
				// if there is a title, draw a title bar
				if (title_ != NULL) {
					unsigned minimumTitleBarHeight = 
							title_->getMinimumHeight();
					if (y < minimumTitleBarHeight) {
						r = windowActiveTitleBar.r;
						g = windowActiveTitleBar.g;
						b = windowActiveTitleBar.b;
					}
					if (y == minimumTitleBarHeight) {
						r = windowBorder.r;
						g = windowBorder.g;
						b = windowBorder.b;
					}
				}
				// draw the window border
				if ((x == 0) || (y == 0) || (x == w_ - 1) || (y == h_ - 1)){
					r = windowBorder.r;
					g = windowBorder.g;
					b = windowBorder.b;
				}
				pixel = SDL_MapRGBA(surfaceActive_->format, r, g, b, 0);
				putPixel(surfaceActive_, x, y, pixel);
			}
		}

		// next the inactive window
		for (y = 0; y < h_; ++y) {
			for (x = 0; x < w_; ++x) {
				// draw a gradient for the window
				r = windowInactiveTop.r - ((windowInactiveTop.r -
						windowInactiveBottom.r) * y / h_);
				g = windowInactiveTop.g - ((windowInactiveTop.g -
						windowInactiveBottom.g) * y / h_);
				b = windowInactiveTop.b - ((windowInactiveTop.b -
						windowInactiveBottom.b) * y / h_);
				// if there is a title, draw a title bar
				if (title_ != NULL) {
					unsigned minimumTitleBarHeight = 
							title_->getMinimumHeight();
					if (y < minimumTitleBarHeight) {
						r = windowInactiveTitleBar.r;
						g = windowInactiveTitleBar.g;
						b = windowInactiveTitleBar.b;
					}
					if (y == minimumTitleBarHeight) {
						r = windowBorder.r;
						g = windowBorder.g;
						b = windowBorder.b;
					}
				}
				// draw the window border
				if ((x == 0) || (y == 0) || (x == w_ - 1) || (y == h_ - 1)) {
					r = windowBorder.r;
					g = windowBorder.g;
					b = windowBorder.b;
				}
				pixel = SDL_MapRGBA(surfaceInactive_->format, r, g, b, 0);
				putPixel(surfaceInactive_, x, y, pixel);
			}
		}
		// if there is a title blit it
		if (title_ != NULL) {
			title_->blit(surfaceActive_);
			title_->blit(surfaceInactive_);
		}
		// clear the full surface to force it to be recreated
		SDL_FreeSurface(surfaceAll_);
		surfaceAll_ = NULL;
	}

	void Window::renderAll() {
		if (surfaceAll_ != NULL)
			SDL_FreeSurface(surfaceAll_);
		surfaceAll_ = SDL_CreateRGBSurface(SDL_SWSURFACE, w_, h_, bpp, 0, 0, 0, 
				0);

		if (SDL_SetAlpha(surfaceAll_, SDL_SRCALPHA, alpha_) < 0) {
			std::cout << "Set alpha failed:" << SDL_GetError() << std::endl;
			exit(1);
		}

		SDL_Rect dest;
		dest.x = 0;
		dest.y = 0;
		if (isActive_ == true) {
			if (SDL_BlitSurface(surfaceActive_, NULL, surfaceAll_, &dest) < 0) {
				std::cout << "surface blit failed:" << SDL_GetError() << "\n";
				exit(1);
			}
		} else {
			if (SDL_BlitSurface(surfaceInactive_, NULL, surfaceAll_, &dest) < 0) 
			{
				std::cout << "surface blit failed:" << SDL_GetError() << "\n";
				exit(1);
			}
		}
		for (int i = 0; i < (int)textBoxes_.size(); i++) {
			textBoxes_.at(i)->blit(surfaceAll_);
		}
		for (int i = 0; i < (int)inputBoxes_.size(); i++) {
			inputBoxes_.at(i)->blit(surfaceAll_);
		}
		for (int i = 0; i < (int)buttons_.size(); i++) {
			buttons_.at(i)->blit(surfaceAll_);
		}
		for (int i = 0; i < (int)images_.size(); i++) {
			images_.at(i)->blit(surfaceAll_);
		}
	}

	void Window::tick() {
		for (int i = 0; i < (int)textBoxes_.size(); i++) {
			textBoxes_.at(i)->tick();
		}
		for (int i = 0; i < (int)inputBoxes_.size(); i++) {
			inputBoxes_.at(i)->tick();
		}
		for (int i = 0; i < (int)buttons_.size(); i++) {
			buttons_.at(i)->tick();
		}
		for (int i = 0; i < (int)windows_.size(); i++) {
			windows_.at(i)->tick();
		}
		for (int i = 0; i < (int)images_.size(); i++) {
			images_.at(i)->tick();
		}
	}

	void Window::addTextBox(TextBox* textBox) {
		textBoxes_.push_back(textBox);
	}

	void Window::addInputBox(InputBox* inputBox) {
		inputBoxes_.push_back(inputBox);
	}

	void Window::addButton(Button* button) {
		buttons_.push_back(button);
	}

	void Window::addImage(Image* image) {
		images_.push_back(image);
	}

	void Window::setTitle(const char* title) {
		if (title != NULL) {
			if (title_ != NULL) {
				title_->setText(title);
			} else {
				title_ = new TextBox(title, 2, 2, w_ - 2, h_ - 2, 12);
				title_->setCentred();
			}
		} else {
			if (title_ != NULL) {
				delete title_;
				title_ = NULL;
			}
		}
	}

	void Window::setX(const int x) {
		x_ = x;
	}

	void Window::setY(const int y) {
		y_ = y;
	}

	void Window::setWidth(const unsigned int w) {
		isInvalidated_ = true;
		w_ = w;
	}

	int Window::getWidth() const {
		return w_;
	}

	void Window::setHeight(const unsigned int h) {
		isInvalidated_ = true;
		h_ = h;
	}

	int Window::getHeight() const {
		return h_;
	}

	int Window::getX() const {
		return x_;
	}

	int Window::getY() const {
		return y_;
	}

	bool Window::getIsActive() {
		return isActive_;
	}

	void Window::setIsActive(const bool isActive) {
		if (isActive != isActive_) {
			SDL_FreeSurface(surfaceAll_);
			surfaceAll_ = NULL;
			if (isActive == true) {
				for (int i = 0; i < (int)inputBoxes_.size(); i++) {
					inputBoxes_.at(i)->setFillColour(&inputBoxActive);
				}
				for (int i = 0; i < (int)buttons_.size(); i++) {
					buttons_.at(i)->setColour(&buttonActive);
				}
			} else {
				for (int i = 0; i < (int)inputBoxes_.size(); i++) {
					inputBoxes_.at(i)->setFillColour(&inputBoxInactive);
				}
				for (int i = 0; i < (int)buttons_.size(); i++) {
					buttons_.at(i)->setColour(&buttonInactive);
				}
			}

		}
		isActive_ = isActive;
//		std::cout<<isActive_<<" "<<isInvalidated_<<std::endl;
	}

	bool Window::keyEvent(const SDL_KeyboardEvent &event) {
		for (int i = 0; i < (int)inputBoxes_.size(); i++) {
			if (inputBoxes_.at(i)->getIsActive() == true) {
				inputBoxes_.at(i)->keyEvent(event);
				return true;
			}
		}
		return false;
	}

	bool Window::mouseMotionEvent(const SDL_MouseMotionEvent &motionEvent) {
		bool result = false;
		return result;
	}

	bool Window::mouseButtonEvent(const SDL_MouseButtonEvent &buttonEvent) {
		bool result = false;
		int x = buttonEvent.x - x_;
		int y = buttonEvent.y - y_;
		if (buttonEvent.type == SDL_MOUSEBUTTONUP) {
			for (int i = 0; i < (int)buttons_.size(); i++) {
				buttons_.at(i)->release();
			}
		} else {
			for (int i = 0; i < (int)inputBoxes_.size(); i++) {
				// was the click on a window?
				if ((x > inputBoxes_.at(i)->getX()) && 
						(x < inputBoxes_.at(i)->getX() + 
						inputBoxes_.at(i)->getWidth()) 	&&  
						(y > inputBoxes_.at(i)->getY()) &&
						(y < inputBoxes_.at(i)->getY() + 
						inputBoxes_.at(i)->getHeight())) {
					inputBoxes_.at(i)->setIsActive(true);
					result = true;
				} else {
					inputBoxes_.at(i)->setIsActive(false);
				}
			}
			for (int i = 0; i < (int)buttons_.size(); i++) {
				// was the click on a button?
				if ((x > buttons_.at(i)->getX()) && 
						(x < buttons_.at(i)->getX() + 
						buttons_.at(i)->getWidth()) 	&&  
						(y > buttons_.at(i)->getY()) &&
						(y < buttons_.at(i)->getY() + 
						buttons_.at(i)->getHeight())) {
					buttons_.at(i)->click();
					result = true;
				}
			}
			if (buttonEvent.button == SDL_BUTTON_WHEELUP) {
				for (int i = 0; i < (int)textBoxes_.size(); i++) {
					// was the click on a button?
					if ((x > textBoxes_.at(i)->getX()) && 
							(x < textBoxes_.at(i)->getX() + 
							textBoxes_.at(i)->getWidth()) 	&&  
							(y > textBoxes_.at(i)->getY()) &&
							(y < textBoxes_.at(i)->getY() + 
							textBoxes_.at(i)->getHeight())) {
						textBoxes_.at(i)->setView(
								textBoxes_.at(i)->getView() - 1);
						result = true;
					}
				}
			}
			if (buttonEvent.button == SDL_BUTTON_WHEELDOWN) {
				for (int i = 0; i < (int)textBoxes_.size(); i++) {
					// was the click on a button?
					if ((x > textBoxes_.at(i)->getX()) && 
							(x < textBoxes_.at(i)->getX() + 
							textBoxes_.at(i)->getWidth()) 	&&  
							(y > textBoxes_.at(i)->getY()) &&
							(y < textBoxes_.at(i)->getY() + 
							textBoxes_.at(i)->getHeight())) {
						textBoxes_.at(i)->setView(
								textBoxes_.at(i)->getView() + 1);
						result = true;
					}
				}
			}		
		}
		return result;
	}




/*
	bool Window::mouseEvent(Uint8 buttons, int x, int y) {
		bool result = false;
		if (buttons & SDL_BUTTON(1)) {
			for (int i = 0; i < (int)inputBoxes_.size(); i++) {
				// was the click on an inputbox?
				if ((x > inputBoxes_.at(i)->getX()) && 
						(x < inputBoxes_.at(i)->getX() + 
						inputBoxes_.at(i)->getWidth()) 	&&  
						(y > inputBoxes_.at(i)->getY()) &&
						(y < inputBoxes_.at(i)->getY() + 
						inputBoxes_.at(i)->getHeight())) {
					result = true;
					inputBoxes_.at(i)->setIsActive(true);
				} else {
					inputBoxes_.at(i)->setIsActive(false);
				}
			}
			for (int i = 0; i < (int)buttons_.size(); i++) {
				// was the click on a button?
				if ((x > buttons_.at(i)->getX()) && 
						(x < buttons_.at(i)->getX() + 
						buttons_.at(i)->getWidth()) 	&&  
						(y > buttons_.at(i)->getY()) &&
						(y < buttons_.at(i)->getY() + 
						buttons_.at(i)->getHeight())) {
					buttons_.at(i)->click();
					result = true;
				}
			}
		} else {
			for (int i = 0; i < (int)buttons_.size(); i++) {
				buttons_.at(i)->release();
			}
		}
		return result;
	}
*/
	// UI ======================================================================
	UI::UI() {
		isWindowDragged_ = false;
		windowDragged_ = 0;
	}

	UI::~UI() {
		while(textBoxes_.size() > 0) {
			delete textBoxes_.front();
			textBoxes_.erase(textBoxes_.begin());
		}
		while(inputBoxes_.size() > 0) {
			delete inputBoxes_.front();
			inputBoxes_.erase(inputBoxes_.begin());
		}
		while(buttons_.size() > 0) {
			delete buttons_.front();
			buttons_.erase(buttons_.begin());
		}
		while(windows_.size() > 0) {
			delete windows_.front();
			windows_.erase(windows_.begin());
		}
	}

	bool UI::keyEvent(const SDL_KeyboardEvent &event) {
		// send the keys to the active window
		for (int i = 0; i < (int)windows_.size(); i++) {
			if (windows_.at(i)->getIsActive()) {
				return windows_.at(i)->keyEvent(event);
			}
		}
		return false;
	}

	bool UI::mouseMotionEvent(const SDL_MouseMotionEvent &motionEvent) {
		bool result = false;
		if (isWindowDragged_ == true) {
			windows_.at(windowDragged_)->setX(windows_.at(windowDragged_)->getX() + motionEvent.xrel);
			windows_.at(windowDragged_)->setY(windows_.at(windowDragged_)->getY() + motionEvent.yrel);
			result = true;
		}
		return result;
	}

	bool UI::mouseButtonEvent(const SDL_MouseButtonEvent &buttonEvent) {
		bool result = false;
		if (buttonEvent.type == SDL_MOUSEBUTTONUP) {
			isWindowDragged_ = false;
			result = true;
		}
		for (int i = 0; i < (int)windows_.size(); i++) {
			// was the event on a window?
			if ((buttonEvent.x > windows_.at(i)->getX()) && 
					(buttonEvent.x < windows_.at(i)->getX() + 
					windows_.at(i)->getWidth()) 	&&  
					(buttonEvent.y > windows_.at(i)->getY()) &&
					(buttonEvent.y < windows_.at(i)->getY() + 
					windows_.at(i)->getHeight())) {
				windows_.at(i)->mouseButtonEvent(buttonEvent);
				if (buttonEvent.type == SDL_MOUSEBUTTONDOWN) {
					windows_.at(i)->setIsActive(true);
					windowDragged_ = i;
					isWindowDragged_ = true;
				}
				result = true;
			} else {
				if (buttonEvent.type == SDL_MOUSEBUTTONDOWN)
					windows_.at(i)->setIsActive(false);
			}
		}
		return result;
	}

	void UI::blit(SDL_Surface* surface) {
		for (int i = 0; i < (int)textBoxes_.size(); i++) {
			textBoxes_.at(i)->blit(surface);
		}
		for (int i = 0; i < (int)inputBoxes_.size(); i++) {
			inputBoxes_.at(i)->blit(surface);
		}
		for (int i = 0; i < (int)buttons_.size(); i++) {
			buttons_.at(i)->blit(surface);
		}
		for (int i = 0; i < (int)windows_.size(); i++) {
			windows_.at(i)->blit(surface);
		}
	}

	void UI::tick() {
		for (int i = 0; i < (int)textBoxes_.size(); i++) {
			textBoxes_.at(i)->tick();
		}
		for (int i = 0; i < (int)inputBoxes_.size(); i++) {
			inputBoxes_.at(i)->tick();
		}
		for (int i = 0; i < (int)buttons_.size(); i++) {
			buttons_.at(i)->tick();
		}
		for (int i = 0; i < (int)windows_.size(); i++) {
			windows_.at(i)->tick();
		}
	}

	void UI::addTextBox(TextBox* textBox) {
		textBoxes_.push_back(textBox);
	}

	void UI::addInputBox(InputBox* inputBox) {
		inputBoxes_.push_back(inputBox);
	}

	void UI::addButton(Button* button) {
		buttons_.push_back(button);
	}

	void UI::addWindow(Window* window) {
		windows_.push_back(window);
	}

}
